//
// Created by Administrator on 2021/11/10.
//

#include "Camera.h"

void Camera::Setup(
        float aspect,
        float fov,
        float near,
        float far)
{
    m_Aspect = aspect;
    m_Fov = fov;
    m_Near = near;
    m_Far = far;

    BuildCanvas();
    BuildCameraMatrix();
    BuildProjectionMatrix();
}

const Mat4x4f& Camera::cameraToWorldMatrix()
{
    if(m_CameraDirty || m_transform.l2wDirty())
    {
        BuildCameraMatrix();
        m_CameraDirty = false;
    }
    return m_CameraToWorldMatrix;
}

const Mat4x4f& Camera::worldToCameraMatrix()
{
    if(m_CameraDirty || m_transform.l2wDirty())
    {
        BuildCameraMatrix();
        m_CameraDirty = false;
    }
    return m_WorldToCameraMatrix;
}

const Mat4x4f& Camera::projectionMatrix()
{
    if(m_ProjDirty)
    {
        BuildProjectionMatrix();
        m_ProjDirty = false;
    }
    return m_ProjectionMatrix;
}

void Camera::BuildCameraMatrix()
{
    // camera to world
    Vec3f w = m_transform.forward();
    Vec3f u = m_transform.right();
    Vec3f v = m_transform.up();
    Vec3f p = m_transform.position();
//    Mat4x4f rotation = Mat4x4f(u.x(), v.x(), w.x(), 0.0f,
//                               u.y(), v.y(), w.y(), 0.0f,
//                               u.z(), v.z(), w.z(), 0.0f,
//                               0.0f, 0.0f, 0.0f, 1.0f);
//    Mat4x4f translation = Mat4x4f(1.0f, 0.0f, 0.0f, p.x(),
//                                  0.0f, 1.0f, 0.0f, p.y(),
//                                  0.0f, 0.0f, 1.0f, p.z(),
//                                  0.0f, 0.0f, 0.0f, 1.0f);
//    m_CameraToWorldMatrix = translation * rotation;

    // 可以直接编码TR矩阵（省去上面的计算）
    m_CameraToWorldMatrix = Mat4x4f(u.x(), v.x(), w.x(), p.x(),
                                    u.y(), v.y(), w.y(), p.y(),
                                    u.z(), v.z(), w.z(), p.z(),
                                    0.0f, 0.0f, 0.0f, 1.0f);

    //world to camera
    //(TR)^-1 = R^-1T^-1
//    Mat4x4f translation = Mat4x4f(1.0f, 0.0f, 0.0f, -p.x(),
//                                  0.0f, 1.0f, 0.0f, -p.y(),
//                                  0.0f, 0.0f, 1.0f, -p.z(),
//                                  0.0f, 0.0f, 0.0f, 1.0f);
//    rotation = Mat4x4f(u.x(), u.y(), u.z(), 0.0f,
//                       v.x(), v.y(), v.z(), 0.0f,
//                       w.x(), w.y(), w.z(), 0.0f,
//                       0.0f, 0.0f, 0.0f, 1.0f);
//    m_WorldToCameraMatrix = rotation * translation;
    //也可以直接编码
    m_WorldToCameraMatrix = Mat4x4f(u.x(), u.y(), u.z(), -u.Dot(p),
                                    v.x(), v.y(), v.z(), -v.Dot(p),
                                    w.x(), w.y(), w.z(), -w.Dot(p),
                                    0.0f,  0.0f,  0.0f,  1.0f);
}

void Camera::BuildProjectionMatrix()
{
    // en 写
    // 推导的话，x和y坐标，要投影到Screen Coordinate，可在投影平面和原来位置坐标的截面构造相似三角形，
    // 这样可以推出xp = n * x / -z; 同理yp = n * y / -z
    // 接着再变换到NDC空间[-1, 1]范围内
    // 不过在opengl中，投影矩阵实际上并不是直接把Camera Space下的坐标变换到NDC空间，而是变换到
    // 一个[-1, 1]^3的齐次坐标空间下的Cube中（这里坐标都用齐次坐标表示，因此所谓的[-1, 1]实际上是进行了/w后为[-1, 1]，实际的坐标是乘了w的)
    // 因此需要构造一个能使点除-z的矩阵，但这种矩阵是不存在的，因此只能退而求其次，将除以-z的任务交给齐次坐标点
    // 使得矩阵乘上原坐标后得到一个w等于-z的齐次坐标，因为齐次坐标变换为笛卡尔坐标，本来就需要除以w分量，
    // 如果w分量为-z，那么显然最终得到的齐次坐标就能自然而然的通过除z获得透视除法后的笛卡尔坐标。
    // 因此，这个透视矩阵，实际上是将点变换到齐次坐标空间中（顺便在这里进行Clipping，所以也叫齐次裁剪空间）。
    // 如何构造这个矩阵呢？方法有很多，说一个比较直观的吧。
    // 先考虑第一行，矩阵第一行与点坐标相乘，获得最终的x坐标。
    // 这里可用逆向思维推出。首先，最终的NDC Space(透视除法之后）x坐标记为Xn, Clip Space为Xc, Screen Space的x坐标记录为Xs, Camera Space的x坐标记录为Xp
    // 先考虑从Xs->Xn的变换，这个很简单，就是一个Window变换（TRT矩阵），从[l, r] -> [-1, 1]
    // 因此最后这第一行就是[2 / (r - l), 0, 0, -(r + l) / (r - l)]
    // 将Xs带入，则有Xn = 2 * Xs / (r - l) - (r + l) / (r - l)
    // 又因为Xs = n * Xp / -Zp(Camera Space的z坐标), 带入得
    // Xn = 2 * n * Xp / -Zp * (r - l) - (r + l) / (r - l)
    // 这样就能获得Xp直接到Xn的变换，但由于我们最终需要的实际上是未/w的齐次坐标，这个空间下的齐次坐标的w是-Pz（由前面可得）,因此需要乘回来，有
    // Xc = Xn * Wp = Xn * -Zp = 2 * n * Xp * -Zp / -Zp * (r - l) - (r + l) * (-Zp) / (r - l)
    // 化简得Xc = 2 * n * Xp / (r - l) + (r + l) * Zp / (r - l)
    // 从上式提取出矩阵系数，则Xp到Xc 矩阵的第一行为[2 * n / (r - l), 0, (r + l) / (r - l), 0]
    // 同理可得Yp到Yc 矩阵的第二行为[0, 2 * n / (t - b), (t + b) / (t - b), 0]
    // z的话 z坐标为-near的点会投影到-near(透视除法后变为-1), -far的点会投影到far(透视除法后变为1)，可联立方程组
    // -nA + B = -n   -fA + B = f 可解出A,B
    float r = m_Canvas.r;
    float l = m_Canvas.l;
    float t = m_Canvas.t;
    float b = m_Canvas.b;
    float n = m_Near;
    float f = m_Far;
    m_ProjectionMatrix = Mat4x4f(2.0f * n / (r - l), 0.0f,               (r + l) / (r - l), 0.0f,
                                 0.0f,               2.0f * n / (t - b), (t + b) / (t - b), 0.0f,
                                 0.0f,               0.0f,               (n + f) / (n - f), 2 * n * f / (n - f),
                                 0.0f,               0.0f,               -1.0f,             0.0f);
}

void Camera::BuildCanvas()
{
    m_Canvas.t = tan(m_Fov * 0.5f * Deg2Rad) * m_Near;
    m_Canvas.b = -m_Canvas.t;
    m_Canvas.r = m_Canvas.t * m_Aspect;
    m_Canvas.l = -m_Canvas.r;
}